/*=========================================================================

  Program:   Image Guided Surgery Software Toolkit
  Module:    igstkCoordinateSystemDelegator.h
  Language:  C++
  Date:      $Date$
  Version:   $Revision$

  Copyright (c) ISC  Insight Software Consortium.  All rights reserved.
  See IGSTKCopyright.txt or http://www.igstk.org/copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notices for more information.

=========================================================================*/

#ifndef __igstkCoordinateSystemDelegator_h
#define __igstkCoordinateSystemDelegator_h

#include "igstkCoordinateSystem.h"
#include "igstkCoordinateSystemTransformToResult.h"
#include "igstkCoordinateSystemTransformToErrorResult.h"
#include "igstkCoordinateSystemSetTransformResult.h"

namespace igstk
{

/** \class CoordinateSystemDelegator
 *
 *  \brief Encapsulates common logic for handling coordinate reference system
 *  calls on objects with CoordinateSystems. 
 *
 *  Most of the functionality is delegated to CoordinateSystem. This
 *  class is intended to be used inside classes like SpatialObject and View.
 *  This class delegates much of the core coordinate system connectivity and
 *  transformation work to the CoordinateSystem class.
 *
 *  The following diagram illustrates the state machine of 
 *  the CoordinateSystemDelegator class
 *
 *  \image html  igstkCoordinateSystemDelegator.png  
 *  "CoordinateSystemDelegator State Machine Diagram" 
 *
 *  \image latex igstkCoordinateSystemDelegator.eps 
 *  "CoordinateSystemDelegator State Machine Diagram" 
 */
class CoordinateSystemDelegator : public Object
{
public: 

  /** Macro with standard traits declarations. */
  igstkStandardClassTraitsMacro( CoordinateSystemDelegator, Object )
  
  /** This method implements the construction of a coordinate system graph by 
   *  defining the parent of this object and the Transforms defining their
   *  relative position and orientation */
  template < class TParentPointer >
  void RequestSetTransformAndParent( const Transform & transformToParent, 
                                     TParentPointer parent )
    {
    if( !(parent) ) // This expression must be suitable for both 
                    // Smart and Raw pointers.
      {
      igstkPushInputMacro( NullParent );
      m_StateMachine.ProcessInputs();
      return;
      }
    else
      {
      const CoordinateSystem* parentReferenceSystem = 
      igstk::Friends::CoordinateSystemHelper
                                    ::GetCoordinateSystem( parent );

      /** Use event observer to handle the return event */
      this->m_CoordinateSystem->RequestSetTransformAndParent(
                                                      transformToParent,
                                                      parentReferenceSystem);

      return;
      }
    }

  // This method updates the transform between this object and its parent 
  void RequestUpdateTransformToParent( const Transform & transformToParent )
    {
    this->m_CoordinateSystem->RequestUpdateTransformToParent(
                                                         transformToParent);
    }

  /** Returns the transform to the parent if available. */
  void RequestGetTransformToParent();

  /** Detach from its parents */
  void RequestDetachFromParent()
    {
    this->m_CoordinateSystem->RequestDetachFromParent();
    }

  /**
   * Tries to compute the transformation from this coordinate system
   * to another coordinate system. This method is templated on the 
   * input and should allow computation of a transform to another
   * object that internally has a CoordinateSystem. 
   *
   * Three types of events may be generated by this call:
   *     CoordinateSystemTransformToEvent
   *     CoordinateSystemTransformToNullTargetEvent
   *     CoordinateSystemTransformToDisconnectedEvent
   */
  template <class TTarget>
  void RequestComputeTransformTo(const TTarget & target)
    {
    if( !(target) )
      {
      igstkPushInputMacro( NullTarget );
      m_StateMachine.ProcessInputs();
      return;
      }

    /** First get the coordinate system from the target object. */
    const CoordinateSystem* targetCoordSys = 
      igstk::Friends::CoordinateSystemHelper::
                                      GetCoordinateSystem( target );

    /** Handle the response with event observers */
    this->m_CoordinateSystem->RequestComputeTransformTo(
                                                             targetCoordSys);
    }

  /** Typedef for coordinate system*/
  typedef igstk::CoordinateSystem              CoordinateSystemType;

  /** Allows another object to verify which coordinate system 
   *  an object owns. This does not check that the coordinate
   *  systems are equivalent, but whether the instances of 
   *  the coordinate systems are identical.
   */
  bool IsCoordinateSystem( const CoordinateSystemType* ) const;

  /** Print out object information. */
  void PrintSelf( std::ostream& os, itk::Indent indent ) const;

  /** Set/Get the name of the coordinate system. */
  void SetName( const char* name );
  void SetName( const std::string& name );
  const char* GetName() const;

  /** Set/Get the type of the coordinate system. */
  void SetType( const char* type );
  void SetType( const std::string& type );
  const char* GetType() const;
  
protected:
  CoordinateSystemDelegator();
  ~CoordinateSystemDelegator();

private:
  /** Purposely not implemented. */
  CoordinateSystemDelegator(const CoordinateSystemDelegator& );

  /** Purposely not implemented. */
  CoordinateSystemDelegator& operator=(const CoordinateSystemDelegator&);

  /** One state */
  igstkDeclareStateMacro( Idle );

  /** A few inputs. */
  igstkDeclareInputMacro( NullParent );
  igstkDeclareInputMacro( NullTarget );

  /** Declaring frienship with helper that will facilitate enforcing the
   *  privacy of the CoordinateSystem. 
   */
  igstkFriendClassMacro( igstk::Friends::CoordinateSystemHelper );

  /** Private method for getting the CoordinateSystem. This method
   *  is mainly intended to be called from the 
   *  CoordinateSystemHelper as a secure way of passing the
   *  CoordinateSystem without breaking its encapsulation. 
   */
  const CoordinateSystem * GetCoordinateSystem() const;

  /** Pointer to a CoordinateSystem object for managing 
   *  coordinate system operations...
   */ 
  CoordinateSystem::Pointer      m_CoordinateSystem;

  /** Called when RequestSetTransformAndParent is passed a null parent. */
  void NullParentProcessing();

  /** Called when RequestComputeTransformTo is passed a null target. */
  void NullTargetProcessing();

  /** Typedef, Receptor observer, & callback for watching
   *  CoordinateSystem events.
   */
  typedef ::itk::ReceptorMemberCommand< Self > CoordinateSystemObserverType;

  /** Make an observer to watch events on the CoordinateSystem. */
  CoordinateSystemObserverType::Pointer m_CoordinateSystemObserver;

  /** Call back used by the coordinate system observer which
   *  catches events and re-invokes them.
   */
  void ObserverCallback(const ::itk::EventObject & eventvar);

}; // class CoordinateSystemDelegator

}

#endif
